;================================================================
;  hopper.s
;               Exploding pixels falling down a hopper
;
;================================================================
;
; 25thanni, a demo dedicated to the 25th anniversary of the ZX81.
;
; (c)2006 Bodo Wenzel
;
; This program is free software; you can redistribute it and/or
; modify it under the terms of the GNU General Public License as
; published by the Free Software Foundation; either version 2 of
; the License, or (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public
; License along with this program; if not, write to the Free
; Software Foundation Inc., 59 Temple Place, Suite 330, Boston,
; MA 02111-1307 USA
;================================================================

	.module	hopper

;= Externals ====================================================

	.globl	heap_ptr
	.globl	hrg_file

	.globl	FRAME_RATE
	.globl	check_break
	.globl	set_show
	.globl	show_dummy
	.globl	vsync
	.globl	wait_frames

	.globl	CHAR_WIDTH,CHAR_HEIGHT
	.globl	G0,G1,G2,G3,G4,G5,G6,G7,G8,G9,GA,GB,GC,GD,GE,GF
	.globl	__,X8,X9,XA,QU,PD,DL,CL,QM,LP,RP,GT,LT,EQ,PL,MI
	.globl	TI,SL,SC,CM,PE,_0,_1,_2,_3,_4,_5,_6,_7,_8,_9
	.globl	_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P
	.globl	_Q,_R,_S,_T,_U,_V,_W,_X,_Y,_Z,NL,INV
	.globl	render_char

	.globl	HRG_WIDTH,HRG_HEIGHT
	.globl	show_hrg
	.globl	h_in_up
	.globl	h_out_down
	.globl	h_check

	.globl	random

	.globl	MATH_VALUE_SIZE,MATH_SIGN

	.globl	unpack

;= Constants ====================================================

; double global definition checked by linker
FRAME_RATE	==	50
CHAR_WIDTH	==	8
CHAR_HEIGHT	==	8
HRG_WIDTH	==	256
HRG_HEIGHT	==	192
MATH_VALUE_SIZE	==	7

BITS_PER_BYTE	=	8
BYTES_PER_WORD	=	2

HRG_BYTE_WIDTH	=	HRG_WIDTH/BITS_PER_BYTE
HRG_SIZE	=	HRG_HEIGHT*HRG_BYTE_WIDTH

PIXEL_SIZE	=	9
	;	2		; pointer into HRG bitmap
	;	1		; mask of bit in HRG byte
	;	1		; horizontal speed
	;	1		; horizontal position (fraction)
	;	1		; horizontal position
	;	1		; vertical speed
	;	1		; vertical position (fraction)
	;	1		; vertical position
NUMBER_OF_PIXELS=	18	; maximum, see the font!

BG_WIDTH	=	120
BG_HEIGHT	=	143
PIPE_WIDTH	=	16	; must be a multiple of 2
BG_BYTE_WIDTH	=	BG_WIDTH/BITS_PER_BYTE
BG_SIZE		=	BG_HEIGHT*BG_BYTE_WIDTH

	.if	BG_BYTE_WIDTH/PIXEL_SIZE
	.if	NUMBER_OF_PIXELS/2/CHAR_HEIGHT
EXTRA_SIZE	=	NUMBER_OF_PIXELS/2*HRG_BYTE_WIDTH
PIXEL_OFFSET	=	(BG_BYTE_WIDTH-PIXEL_SIZE)/2
PIXEL_FACTOR	=	HRG_BYTE_WIDTH/2
	.else
EXTRA_SIZE	=	CHAR_HEIGHT*HRG_BYTE_WIDTH
	.endif
	.else
	.error	"Code depends on BG_BYTE_WIDTH>=PIXEL_SIZE"
	.endif

START_DELAY	=	FRAME_RATE	; in frames

V_OFFSET	=	44
H_OFFSET	=	(HRG_WIDTH/2-CHAR_WIDTH/2)
HOPPER_OFFSET	=	48
H_SPEED		=	15
H_SPEED_VAR	=	16	; must be a power of 2
V_SPEED		=	15
V_SPEED_VAR	=	16	; must be a power of 2

;= Program code =================================================

	.area	CODE

;- Let pixel explode and fall down a hopper ---------------------

hopper::
	ld	hl,#show_dummy
	call	set_show

;- unpack the half hopper and mirror it - - - - - - - - - - - - -

	ld	hl,(heap_ptr)
h_u_olp:
	ld	a,l
	ld	b,#BITS_PER_BYTE
h_u_ilp:
	rra
	rl	c
	djnz	h_u_ilp
	ld	(hl),c
	inc	l
	jr	nz,h_u_olp	; prepare mirror table

	ld	hl,(heap_ptr)
	ld	de,#HRG_SIZE-BG_SIZE
	add	hl,de
	push	hl
	ld	de,#background
	ld	bc,#PAC_BG_SIZE
	call	unpack

	ld	hl,(heap_ptr)
	ld	de,#(HRG_HEIGHT-BG_HEIGHT)*HRG_BYTE_WIDTH
	add	hl,de
	ex	de,hl
	pop	hl
	ld	b,#BG_HEIGHT
h_m_olp:
	push	bc
	ld	bc,#BG_BYTE_WIDTH
	ldir			; copy line to destination

	push	hl
	ld	l,e
	ld	h,d

	.if	HRG_BYTE_WIDTH-2*BG_BYTE_WIDTH
	xor	a
	ld	b,#HRG_BYTE_WIDTH-2*BG_BYTE_WIDTH
h_m_clp:
	ld	(de),a
	inc	de
	djnz	h_m_clp		; clear space in between
	.endif

	ld	bc,(heap_ptr)
h_m_ilp:
	dec	hl
	ld	c,(hl)
	ld	a,(bc)
	ld	(de),a
	inc	de
	ld	a,l
	and	#HRG_BYTE_WIDTH-1
	jr	nz,h_m_ilp	; mirror graphic

	pop	hl
	pop	bc
	djnz	h_m_olp

	ld	hl,(heap_ptr)
	ld	e,l
	ld	d,h
	ld	(hl),#0
	inc	de
	ld	bc,#(HRG_HEIGHT-BG_HEIGHT)*HRG_BYTE_WIDTH-1
	ldir			; clear upper part

;- prepare graphic  - - - - - - - - - - - - - - - - - - - - - - -

	ld	de,(heap_ptr)
	ld	hl,#HRG_SIZE+EXTRA_SIZE
	add	hl,de
	ld	(hrg_file),hl

	ld	b,#HRG_HEIGHT
	ld	c,#HRG_BYTE_WIDTH
h_stk_loop:
	ld	(hl),e
	inc	hl
	ld	(hl),d
	inc	hl
	ld	a,e
	add	a,c
	ld	e,a
	jr	nc,h_stk_next
	inc	d
h_stk_next:
	djnz	h_stk_loop

	push	de
	ld	(show_hrg),hl
	ld	de,#scroll_line
	ld	bc,#PAC_SL_SIZE
	call	unpack
	pop	de

	ld	hl,#PIXEL_OFFSET
	add	hl,de
	ld	(pixels),hl

	call	h_in_up

	ld	hl,#show_hrg
	call	set_show

	ld	a,#START_DELAY
	call	wait_frames

;- the main loop  - - - - - - - - - - - - - - - - - - - - - - - -

	xor	a
	ld	(quit),a

	ld	de,#text
h_char_loop:
	ld	a,(de)
	inc	de
	push	de

	cp	#NL
	jp	z,h_quit

;- render the next character and set pointers and masks - - - - -

	.if	CHAR_WIDTH-8
	.error	"Code depends on CHAR_WIDTH=8"
	.endif

	ld	hl,#rendered
	ld	c,#1
	call	render_char

	exx
	ld	hl,(heap_ptr)
	ld	bc,#V_OFFSET*HRG_BYTE_WIDTH-HRG_BYTE_WIDTH/2
	add	hl,bc
	ex	de,hl
	ld	hl,(pixels)
	exx
	xor	a		; reset counter of pixels
	ld	c,#CHAR_HEIGHT
h_rc_olp:
	exx
	ex	de,hl
	ld	bc,#HRG_BYTE_WIDTH-1
	add	hl,bc
	ex	de,hl
	ld	c,#1<<(CHAR_WIDTH/2-1)
	exx

	ld	b,#CHAR_WIDTH
h_rc_ilp:
	rlc	(hl)
	exx
	jr	nc,h_rc_white

	inc	a		; count pixels

	ld	(hl),e
	inc	hl
	ld	(hl),d
	inc	hl
	ld	(hl),c		; store pointer and mask

	ld	b,#PIXEL_FACTOR-2
h_rc_plp:
	inc	hl
	djnz	h_rc_plp	; advance to next pixel

h_rc_white:
	rrc	c
	jr	nc,h_rc_next
	inc	de
h_rc_next:
	exx
	djnz	h_rc_ilp

	inc	hl

	dec	c
	jr	nz,h_rc_olp

;- mark unused pixels - - - - - - - - - - - - - - - - - - - - - -

	ld	(pixel_count),a

	exx
	inc	hl
	inc	hl
h_fu_loop:
	cp	#NUMBER_OF_PIXELS
	jr	z,h_fu_quit

	inc	a
	ld	(hl),#0
	ld	bc,#PIXEL_FACTOR
	add	hl,bc
	jr	h_fu_loop

h_fu_quit:
	exx

;- set speeds and positions - - - - - - - - - - - - - - - - - - -

	ld	hl,#rendered
	exx
	ld	hl,(pixels)
	ld	bc,#3
	add	hl,bc
	ld	de,#((-7*V_SPEED)<<BITS_PER_BYTE)+V_OFFSET
	exx
	ld	c,#CHAR_HEIGHT
h_sp_olp:
	exx
	ld	bc,#((-7*H_SPEED)<<BITS_PER_BYTE)+H_OFFSET
	exx
	ld	b,#CHAR_WIDTH
h_sp_ilp:
	rlc	(hl)
	exx
	jr	nc,h_sp_next

	call	random
	and	#H_SPEED_VAR-1
	sub	#H_SPEED_VAR/2
	add	a,b
	ld	(hl),a		; horizontal speed
	inc	hl
	ld	(hl),#0		; horizontal position (fraction)
	inc	hl
	ld	(hl),c		; horizontal position
	inc	hl
	call	random
	and	#V_SPEED_VAR-1
	sub	#V_SPEED_VAR/2
	add	a,d
	ld	(hl),a		; vertical speed
	inc	hl
	ld	(hl),#0		; vertical position (fraction)
	inc	hl
	ld	(hl),e		; vertical position

	ld	a,#PIXEL_FACTOR-5
h_sp_plp:
	inc	hl
	dec	a
	jr	nz,h_sp_plp	; advance to next pixel

h_sp_next:
	ld	a,b
	add	a,#2*H_SPEED
	ld	b,a
	inc	c
	exx

	djnz	h_sp_ilp

	inc	hl

	exx
	ld	a,d
	add	a,#2*V_SPEED
	ld	d,a
	inc	e
	exx

	dec	c
	jr	nz,h_sp_olp

;- let the character rise - - - - - - - - - - - - - - - - - - - -

	ld	hl,(heap_ptr)
	ld	de,#HRG_SIZE+HRG_BYTE_WIDTH/2+1-HRG_BYTE_WIDTH
	add	hl,de
	ex	de,hl
	ld	hl,#rendered

	ld	bc,#CHAR_HEIGHT
h_rr_loop:
	ld	a,e
	add	a,#HRG_BYTE_WIDTH-2
	ld	e,a
	jr	nc,h_rr_no_ovr
	inc	d
h_rr_no_ovr:

	xor	a
	rld
	ld	(de),a
	inc	de
	ldi
	jp	pe,h_rr_loop	; copy the pixel into extra space

	ld	c,#HRG_HEIGHT-V_OFFSET
h_ru_olp:
	call	vsync

	call	check_abort

	exx
	ld	hl,(heap_ptr)
	ld	de,#V_OFFSET*HRG_BYTE_WIDTH+HRG_BYTE_WIDTH/2-1
	add	hl,de
	ld	a,#HRG_HEIGHT-V_OFFSET+CHAR_HEIGHT-1
h_ru_ilp:
	ex	de,hl
	ld	hl,#HRG_BYTE_WIDTH
	add	hl,de
	ldi
	ldi
	dec	hl
	dec	hl
	dec	a
	jr	nz,h_ru_ilp
	exx

	dec	c
	jr	nz,h_ru_olp	; and shift it up

;- let the character explode and the pixel fall down  - - - - - -

h_fall_loop:
	call	vsync

	call	check_abort

	ld	hl,(pixels)
	ld	bc,#NUMBER_OF_PIXELS<<BITS_PER_BYTE
h_pix_loop:
	ld	e,(hl)
	inc	hl
	ld	d,(hl)		; pointer
	inc	hl
	ld	a,(hl)		; mask
	and	a
	jp	z,h_pix_next

	ld	a,(de)
	xor	(hl)		; remove pixel
	ld	(de),a

	push	hl
	exx
	pop	hl
	inc	hl

;- advance horizontally - - - - - - - - - - - - - - - - - - - - -

	ld	a,(hl)		; horizontal speed
	add	a,a
	jr	c,h_ah_neg

h_ah_pos:
	inc	hl
	add	a,(hl)
	ld	(hl),a		; horizontal position (fraction)
	inc	hl
	jr	nc,h_ah_quit

	inc	(hl)		; horizontal position
	exx
	rrc	(hl)
	jr	nc,h_ah_p_quit
	inc	de
	dec	hl
	ld	(hl),d
	dec	hl
	ld	(hl),e		; track pointer and mask
	inc	hl
	inc	hl
	jr	h_ah_p_quit

h_ah_neg:
	inc	hl
	add	a,(hl)
	ld	(hl),a		; horizontal position (fraction)
	inc	hl
	jr	c,h_ah_quit

	dec	(hl)		; horizontal position
	exx
	rlc	(hl)
	jr	nc,h_ah_p_quit
	dec	de
	dec	hl
	ld	(hl),d
	dec	hl
	ld	(hl),e		; track pointer and mask
	inc	hl
	inc	hl
h_ah_p_quit:
	exx

h_ah_quit:
	inc	hl

;- advance vertically - - - - - - - - - - - - - - - - - - - - - -

	ld	a,(hl)		; vertical speed
	cp	#(1<<MATH_VALUE_SIZE)-1
	jr	z,h_av_no_acc
	inc	a
	ld	(hl),a		; this is gravity, man!
h_av_no_acc:
	inc	hl
	add	a,a
	jr	c,h_av_neg

h_av_pos:
	add	a,(hl)
	ld	(hl),a		; vertical position (fraction)
	inc	hl
	jr	nc,h_av_quit

	ld	a,(hl)
	cp	#HRG_HEIGHT
	jr	z,h_av_stop	; completely fallen down?

	inc	a
	ld	(hl),a		; vertical position
	exx
	dec	hl
	dec	hl
	ld	a,e
	add	a,#HRG_BYTE_WIDTH
	ld	e,a
	jr	nc,h_av_p_quit
	inc	d
	inc	hl
	ld	(hl),d		; track pointer
	dec	hl
	jr	h_av_p_quit

h_av_neg:
	add	a,(hl)
	ld	(hl),a		; vertical position (fraction)
	inc	hl
	jr	c,h_av_quit

	dec	(hl)		; vertical position
	exx
	dec	hl
	dec	hl
	ld	a,e
	sub	#HRG_BYTE_WIDTH
	ld	e,a
	jr	nc,h_av_p_quit
	dec	d
	inc	hl
	ld	(hl),d		; track pointer
	dec	hl
h_av_p_quit:
	ld	(hl),e
	inc	hl
	inc	hl
	exx
	jr	h_av_quit

h_av_stop:
	exx
	ld	(hl),#0		; clear mask
	exx

h_av_quit:

;- check for limit and bounce - - - - - - - - - - - - - - - - - -

	ld	a,(hl)		; vertical position
	sub	#HOPPER_OFFSET
	jr	c,h_lb_quit	; over the hopper?

	dec	hl
	dec	hl
	cp	#HRG_WIDTH/2-PIPE_WIDTH/2
	jr	nc,h_lb_pipe	; in the pipe?

	ld	d,a
	ld	a,(hl)		; vertical speed
	xor	#MATH_SIGN
	ld	e,a
	dec	hl
	ld	a,(hl)		; horizontal position
	ld	c,a
	dec	hl
	dec	hl
	rla
	jr	c,h_lb_right

h_lb_left:
	ld	a,(hl)		; horizontal speed
	ld	b,a
	xor	#MATH_SIGN
	cp	e
	jr	nc,h_lb_quit	; flying away?

	ld	a,d		; vertical position
	cp	c		; horizontal position
	jr	c,h_lb_quit	; not at the hopper border?

	inc	hl
	inc	hl
	inc	hl
	ld	a,b
	sra	b
	sra	b
	sub	b
	ld	b,(hl)
	ld	(hl),a		; vertical speed
	dec	hl
	dec	hl
	dec	hl
	ld	a,b
	sra	b
	sra	b
	sub	b
	ld	(hl),a		; horizontal speed
	jr	h_lb_quit

h_lb_right:
	ld	a,(hl)		; horizontal speed
	neg
	ld	b,a
	xor	#MATH_SIGN
	cp	e
	jr	nc,h_lb_quit	; flying away?

	ld	a,d		; vertical position
	cpl
	inc	c
	cp	c		; horizontal position
	jr	nc,h_lb_quit	; not at hopper border?

	inc	hl
	inc	hl
	inc	hl
	ld	a,b
	sra	b
	sra	b
	sub	b
	ld	b,(hl)
	ld	(hl),a		; vertical speed
	dec	hl
	dec	hl
	dec	hl
	ld	a,b
	sra	b
	sra	b
	sub	b
	neg
	ld	(hl),a		; horizontal speed
	jr	h_lb_quit

h_lb_pipe:
	dec	hl
	ld	a,(hl)		; horizontal position
	add	a,a
	jr	c,h_lb_p_check
	cpl			; make absolute (tricky!)
h_lb_p_check:
	or	#1
	cp	#PIPE_WIDTH+1
	jr	c,h_lb_quit	; not at pipe border?

	dec	hl
	dec	hl
	ld	a,(hl)
	neg
	ld	(hl),a		; horizontal speed

h_lb_quit:

;- end of the pixel loop  - - - - - - - - - - - - - - - - - - - -

	exx

	ld	a,(de)
	xor	(hl)		; draw pixel
	ld	(de),a

h_pix_next:
	ld	a,c
	or	(hl)
	ld	c,a		; collect pixel masks

	ld	de,#PIXEL_FACTOR-2
	add	hl,de

	dec	b
	jp	nz,h_pix_loop

	ld	a,c
	and	a
	jp	nz,h_fall_loop	; still some pixels falling?

	pop	de

	jp	h_char_loop

;- end of all - - - - - - - - - - - - - - - - - - - - - - - - - -

h_quit:
	call	abort
	jr	h_quit

;= Check for break and quitting =================================

check_abort:
	ld	a,(quit)
	and	a
	jr	nz,abort

	call	check_break
	ret	c

abort:
	ld	hl,#quit
	ld	a,(hl)
	ld	(hl),#0xFF
	and	a
	call	z,h_out_down

	call	h_check
	ret	nz		; roll out not finished?

	pop	de		; return address
	pop	de		; pushed DE
	ret

;= Data =========================================================

background:
	.include	"hopp_bg.inc"
PAC_BG_SIZE	=	.-background
UNP_BG_SIZE	=	UNPACKED
	.if	UNP_BG_SIZE-BG_SIZE
	.error	"Wrong size of background"
	.endif

text:
	.db	_Z,_X,_8,_1,NL

scroll_line:
	.include	"hopp_sl.inc"
PAC_SL_SIZE	=	.-scroll_line
UNP_SL_SIZE	=	UNPACKED

;= Variables ====================================================

	.area	SCRATCH	(ovr)

rendered:
	.ds	CHAR_HEIGHT

pixel_count:
	.db	0
pixels:
	.dw	0

quit:
	.db	0

;= Heap usage ===================================================

;		HRG bitmap
HEAP =		HRG_SIZE
;		bitmap space for pixels and char to shift up
HEAP =	HEAP +	EXTRA_SIZE
;		pointer table for hrg_file
HEAP =	HEAP +	HRG_HEIGHT*BYTES_PER_WORD
;		unpacked scroll text
HEAP =	HEAP +	UNP_SL_SIZE

	.area	HEAP	(abs,ovr)
	.ds	HEAP

;= The end ======================================================
